define([
    'showdown',
    'flowchart',
    'mathjax',
    'highlight-yaml',
    'ng-showdown',
    'js/factories/factory'
], function(showdown, flowchart) {
    var mdDirective = angular.module('app.directive.markdown', ['ng-showdown', 'app.factory.dialog']);

    function decodeLang(text) {
        return text
            .replace(/¨D/g, '$')
            .replace(/¨T/g, '¨')
            .trim();
    }

    function decodeHtml(text) {
        return text
            .replace(/&amp;/g, '&')
            .replace(/&lt;/g, '<')
            .replace(/&gt;/g, '>')
            .replace(/&nbsp;/g ,' ')
            .replace(/&#39;/g, '\'')
            .replace(/&quot;/g, '"');
    }

    // override emoji
    showdown.subParser('emoji', function (text, options, globals) {
        if (!options.emoji) {
            return text;
        }
        // before emoji
        text = globals.converter._dispatch('emoji.before', text, options, globals);
        // replace
        var emojiRgx = /:([\S]+?):/g;
        text = text.replace(emojiRgx, function (wm, emojiCode) {
            if (showdown.helper.emojis.hasOwnProperty(emojiCode)) {
                return '<span style="font-family:Emoji;font-size:1.5em">' + showdown.helper.emojis[emojiCode] + '</span>';
            }
            return wm;
        });
        // after emoji
        text = globals.converter._dispatch('emoji.after', text, options, globals);

        return text;
    });

    // MathJax.js
    showdown.extension('mathjax', function() {
        var matches = { display: [], inline: [] };
        jQuery(document.head).append(MathJax.Stylesheet());
        jQuery(document.head).append('<style>mjx-container>svg{font-size:1.2em;}</style>');
        return [{
            type: 'lang',
            filter: function(text, converter, options) {
                // filter $ & $$ in code blocks
                var codeMatches = [];
                text = text.replace(/```\s*\w+\s*\n+(((?!```)[^])+)\n+```/gm, function(match) {
                    codeMatches.push(match);
                    return '%code_placeholder' + _.size(codeMatches) + '%';
                });

                // display
                text = text.replace(/([^\\])¨D¨D(((?!¨D¨D)[^])+)[^\\]?¨D¨D/gm, function(wholeMatch, prevMatch, match) {
                    matches.display.push(decodeLang(match));
                    return prevMatch + '%mathjax_display_placeholder' + _.size(matches.display) + '%';
                });
                // inline
                text = text.replace(/([^\\])¨D(((?!¨D)[^\n])+)[^\\]?¨D/g, function(wholeMatch, prevMatch, match) {
                    matches.inline.push(decodeLang(match));
                    return prevMatch + '%mathjax_inline_placeholder' + _.size(matches.inline) + '%';
                });

                _.forEach(codeMatches, function(match, i) {
                    text = text.replace(new RegExp('%code_placeholder' + (i + 1) + '%', 'g'), function() {
                        return match;
                    });
                });

                return text;
            }
        }, {
            type: 'output',
            filter: function(text, converter, options) {
                if (!_.isEmpty(matches.display) || !_.isEmpty(matches.inline)) {
                    // display
                    _.forEach(matches.display, function(match, i) {
                        var pattern = '%mathjax_display_placeholder' + (i + 1) + '%';
                        text = text.replace(new RegExp(pattern, 'g'), function() {
                            MathJax.Reset();
                            return MathJax.Typeset(match, true).outerHTML;
                        });
                    });
                    // inline
                    _.forEach(matches.inline, function(match, i) {
                        var pattern = '%mathjax_inline_placeholder' + (i + 1) + '%';
                        text = text.replace(new RegExp(pattern, 'g'), function() {
                            MathJax.Reset();
                            return MathJax.Typeset(match, false).outerHTML;
                        });
                    });

                    matches.display = [];
                    matches.inline = [];
                }

                return text.replace(/\\\$/g, '$');
            }
        }]
    });

    // flowchart.js
    showdown.extension('flowchart', function() {
        var matches = [];
        jQuery(document.head).append('<style>flow-container{display:block;text-align:center;}</style>');
        return [{
            type: 'lang',
            filter: function(text, converter, options) {
                return text.replace(/```\s*flow\s*\n+(((?!```)[^])+)\n+```/gm, function(wholeMatch, match) {
                    matches.push(decodeLang(match));
                    return '%flowchart_placeholder' + _.size(matches) + '%';
                });
            }
        }, {
            type: 'output',
            filter: function(text, converter, options) {
                if (!_.isEmpty(matches)) {
                    var jqCanvas = jQuery('<div id="__canvas">').css({
                        position: 'absolute',
                        top: '-1000px',
                        left: '-1000px'
                    }).appendTo(document.body);

                    var chart = null;
                    _.forEach(matches, function(match, i) {
                        var pattern = '%flowchart_placeholder' + (i + 1) + '%';
                        text = text.replace(new RegExp(pattern, 'g'), function() {
                            if (match) {
                                if (chart) {
                                    chart.clean();
                                }
                                try {
                                    chart = flowchart.parse(match);
                                    chart.drawSVG('__canvas');
                                    return '<flow-container>' + jqCanvas.html() + '</flow-container>';
                                } catch (e) {
                                    //ignored
                                }
                            }
                            return match;
                        });
                    });

                    matches = [];
                    jqCanvas.remove();
                }

                return text;
            }
        }]
    });

    // highlight.js
    showdown.extension('highlight', function() {
        const classAttr = 'class="';
        return [{
            type: 'output',
            filter: function(text, converter, options) {
                var left  = '<pre><code\\b[^>]*>',
                    right = '</code></pre>',
                    flags = 'g',
                    replacement = function(wholeMatch, match, left, right) {
                        match = decodeHtml(match);
                        var lang = (left.match(/class=\"([^ \"]+)/) || [])[1];

                        if (left.includes(classAttr)) {
                            var attrIndex = left.indexOf(classAttr) + classAttr.length;
                            left = left.slice(0, attrIndex) + 'hljs ' + left.slice(attrIndex);
                        } else {
                            left = left.slice(0, -1) + ' class="hljs">';
                        }

                        if (lang && hljs.getLanguage(lang)) {
                            return left + hljs.highlight(lang, match).value + right;
                        } else {
                            return left + hljs.highlightAuto(match).value + right;
                        }
                    };

                return showdown.helper.replaceRecursiveRegExp(text, replacement, left, right, flags);
            }
        }]
    });

    // bootstrap
    showdown.extension('bootstrap', function() {
        return [{
            type: 'output',
            filter: function(text, converter, options) {
                return text.replace(/<table>/g, '<table class="table table-bordered">')
                    .replace(/<img ([^>]+)>/g, function(whole, match) {
                        var imgsrc = /src="([^"]+)"/g.exec(match)[1];
                        return '<img ' + match + ' onclick="__showImage(\'' + imgsrc + '\')" style="cursor:zoom-in">';
                    });
            }
        }]
    });

    mdDirective.config(['$showdownProvider', function($showdownProvider) {
        $showdownProvider.loadExtension('mathjax');
        $showdownProvider.loadExtension('flowchart');
        $showdownProvider.loadExtension('highlight');
        $showdownProvider.loadExtension('bootstrap');
    }]).run(['$showdown', '$timeout', 'xDialog', function($showdown, $timeout, xDialog) {
        // show image modal
        if (!window['__showImage']) {
            jQuery(document.head).append(
                    '<style>' +
                    '  .x-image-modal { overflow-y: hidden; }' +
                    '  .x-image-modal .modal { overflow: auto; }' +
                    '  .x-image-modal .modal-dialog { position: absolute; width: auto; }' +
                    '</style>');
            var resizeImageModal = function() {
                var jqModal = jQuery('.x-image-modal .modal-dialog');
                var scrollY = parseInt(jQuery('body').css('padding-right'));
                jqModal.css('left', (jQuery(window).width() - scrollY - jqModal.width()) / 2 + 'px');
            }
            window['__showImage'] = function(url) {
                xDialog.modal({
                    backdrop: 'static',
                    openedClass: 'x-image-modal',
                    size: 'md',
                    template: 
                        '<div class="modal-body text-center">' +
                        '  <img ng-src="{{imgsrc}}" style="max-width:100%;cursor:zoom-out" ng-click="close()">' +
                        '</div>',
                    controller: ['$scope', '$uibModalInstance', function ($scope, $uibModalInstance) {
                        $scope.imgsrc = url;
                        $scope.close = function () {
                            $uibModalInstance.close('close');
                        };
                        $timeout(resizeImageModal);
                    }]
                });
            }
            jQuery(window).on('resize', resizeImageModal);
        }
        // markdown options
        angular.extend($showdown.getOptions(), {
            ghCompatibleHeaderId: true,
            prefixHeaderId: true,
            rawHeaderId: true,
            rawPrefixHeaderId: true,
            parseImgDimensions: true,
            simplifiedAutoLink: true,
            strikethrough: true,
            tables: true,
            tablesHeaderId: true,
            ghCodeBlocks: true,
            tasklists: true,
            smoothLivePreview: true,
            smartIndentationFix: true,
            openLinksInNewWindow: true,
            emoji: true,
            underline: true,
            completeHTMLDocument: false,
            metadata: true,
            splitAdjacentBlockquotes: true
        });
    }]);

    return mdDirective;
});
